package bloodfang;

import java.util.ArrayList;
import java.util.Arrays;

import bloodfang.CycleAssassination2Phase.PhaseName;
import bloodfang.data.KeyValueStore;

public class CycleAssassination2 extends CycleRogue {

	private double vendetta_mult = 1.;
	
	private double current_vendetta_cd = vendetta_cd;
	private double current_vanish_cd = vanish_cd;

	public CycleAssassination2(Stats stats, Attacks attacks) {
		this.stats = stats;
		this.attacks = attacks;
		this.stats.getSettings().spec = Spec.ASSASSINATION;
	}
	
	@Override
	public double get_dps() {
		generate_aps();
		generate_dps();
		return dps.sum();
	}

	@Override
	protected void generate_aps() {
		double last_aps = 0.;
		aps.clear();
		
		double base_vanish_cd = vanish_cd;
		double base_vendetta_cd = vendetta_cd;
		
		double cooldownRecoveryRate = stats.getCooldownRecoveryRate();
		base_vanish_cd /= (1. + cooldownRecoveryRate);
		base_vendetta_cd /= (1. + cooldownRecoveryRate);
		
		current_vanish_cd = base_vanish_cd + stats.getSettings().cooldown_delay;
		current_vendetta_cd = base_vendetta_cd + stats.getSettings().cooldown_delay;
		
		double vendetta_duration = 20.;
		double vendetta_dmg = 0.3;
		if(true /* TODO stats.hasGlyph("vendetta")*/){
			vendetta_duration += 10.;
			vendetta_dmg -= 0.05;
		}
		
		while(true){
			aps.clear(); // do not remove
			double energy_regen = get_energy_regen();
			
			CycleAssassinationSetup2 mutilate = new CycleAssassinationSetup2();
			mutilate.main_cpg = "mutilate";
			mutilate.energy_regen = energy_regen;
				
			CycleAssassinationSetup2 mutilate_w_v = new CycleAssassinationSetup2();
			mutilate_w_v.main_cpg = "mutilate";
			mutilate_w_v.vendetta = true;
			mutilate_w_v.energy_regen = energy_regen;
			
			CycleAssassinationSetup2 dispatch = new CycleAssassinationSetup2();
			dispatch.main_cpg = "dispatch";
			dispatch.energy_regen = energy_regen;
			
			CycleAssassinationSetup2 dispatch_w_v = new CycleAssassinationSetup2();
			dispatch_w_v.main_cpg = "dispatch";
			dispatch_w_v.vendetta = true;
			dispatch_w_v.energy_regen = energy_regen;
			
			ArrayList<CycleAssassination2Phase> phases = new ArrayList<CycleAssassination2Phase>();
			double time_left_fight = 360.; //stats.getSettings().FIGHT_LENGTH;
			double active_vendetta_cd = 0.;
			while(time_left_fight > 0.0001){
				if(active_vendetta_cd <= 0.001){
//					System.out.println("perform vendetta " + time_left_fight);
					active_vendetta_cd = current_vendetta_cd;
					active_vendetta_cd -= vendetta_duration;
					double length = vendetta_duration;
					if(length > time_left_fight)
						length = time_left_fight;
					CycleAssassination2Phase phase = new CycleAssassination2Phase(PhaseName.v);
					phase.time_mark = time_left_fight;
					phase.length = length;
					phases.add(phase);
					time_left_fight -= length;
				}else{
//					System.out.println("perform normal " + time_left_fight);
					double vendetta_to_finish = active_vendetta_cd;
					//System.out.println("cds: ks: " + ks_to_finish);
					double length = vendetta_to_finish;
					active_vendetta_cd -= length;
					if(length > time_left_fight)
						length = time_left_fight;
					//System.out.println(active_ks_cd);
					CycleAssassination2Phase phase3 = new CycleAssassination2Phase(PhaseName.normal);
					phase3.time_mark = time_left_fight;
					phase3.length = length;
					phases.add(phase3);
					time_left_fight -= length;
				}
			}
			
			double all_phase_length = 0;
			double dur_vendetta = 0;
			for(CycleAssassination2Phase p : phases){
				if(p.name == PhaseName.v)
					dur_vendetta += p.length;
				all_phase_length += p.length;
			}
			double cycle_duration = all_phase_length;
			double normal_duration = cycle_duration - dur_vendetta;
			
			// TODO EXECUTE_TIME
			double execute_time = 0.35;
			
			// pre calc of cycles
			KeyValueStore template_mutilate_aps = get_cycle_aps(mutilate);
			KeyValueStore template_v_mutilate_aps = get_cycle_aps(mutilate_w_v);
			KeyValueStore template_dispatch_aps = get_cycle_aps(dispatch);
			KeyValueStore template_v_dispatch_aps = get_cycle_aps(dispatch_w_v);
			
			for(String attack : template_v_mutilate_aps.keySet()){
				aps.add(attack, template_v_mutilate_aps.get(attack) * (dur_vendetta/cycle_duration) * (1 - execute_time));
			}
			
			for(String attack : template_mutilate_aps.keySet()){
				aps.add(attack, template_mutilate_aps.get(attack) * (normal_duration/cycle_duration) * (1 - execute_time));
			}
				
			for(String attack : template_v_dispatch_aps.keySet()){
				aps.add(attack, template_v_dispatch_aps.get(attack) * (dur_vendetta/cycle_duration) * execute_time);
			}
			
			for(String attack : template_dispatch_aps.keySet()){
				aps.add(attack, template_dispatch_aps.get(attack) * (normal_duration/cycle_duration) * execute_time);
			}
			
			aps.add("rupture", 1./2 );
			double vw_chance = 0.75;
			// perk venomous wounds
			if(stats.getSettings().player_level >= 100) {
				vw_chance = 1.;
			}
			aps.add("venomous_wound", aps.get("rupture") * vw_chance);
			
			double mh_aps = aps.get("mh") + aps.get("mutilate_mh") + aps.get("dispatch") + aps.get("envenom");
			double oh_aps = aps.get("oh") + aps.get("mutilate_oh");
			
			double ip_aps = aps.get("instant_poison");
			aps.add("deadly_poison", 1./3);
			
			this.procs(mh_aps, oh_aps, ip_aps);
			
			// calc vendatta multiplier
			vendetta_mult = 1. + vendetta_dmg * dur_vendetta / cycle_duration; 
			
			if(Math.abs(last_aps - aps.sum()) < PRECISION)
				break;
			last_aps = aps.sum();
		}
	}
	
	private KeyValueStore get_cycle_aps(CycleAssassinationSetup2 template_mutilate_aps){
		KeyValueStore aps = new KeyValueStore();
		double total_speed = this.calc_total_speed();
		
		double mh_hits_per_sec = 0.;
		double oh_hits_per_sec = 0.;
		double mh_swing_per_sec = total_speed / stats.getMh().getSpeed();
		double oh_swing_per_sec = total_speed / stats.getOh().getSpeed();
		
		mh_hits_per_sec = mh_swing_per_sec * this.stats.getMhHitChance();
		oh_hits_per_sec = oh_swing_per_sec * this.stats.getOhHitChance();
		aps.add("mh", mh_hits_per_sec);
		aps.add("oh", oh_hits_per_sec);

		// seal fate
		double crit_chance_mh = attacks.get_dmg("mutilate_mh").getCritChance();
		double crit_chance_oh = attacks.get_dmg("mutilate_oh").getCritChance();
		double mut_crit = 1 - ( 1 - crit_chance_mh) * ( 1 - crit_chance_oh);
		double dp_crit = attacks.get_dmg("dispatch").getCritChance();
		
		// blindside
		double blindside_proc_chance = 0.3 * stats.getMhStyleHitChance();
		
		double vw_regen = 10 * 1. / 2 * 0.75;
		double active_energy_regen = template_mutilate_aps.energy_regen + vw_regen;
		
		double finisher_size = 5.;
		String cpg = template_mutilate_aps.main_cpg;
		double cp_per_cpg = 2;
		double base_ec_cpg = 55;
		if(cpg.equals("mutilate")){
			cp_per_cpg = 2 + mut_crit + blindside_proc_chance * (1 + dp_crit);
			base_ec_cpg = 55;
			if(stats.hasSetBonus(SetBonus.T16_2PC)){
				base_ec_cpg -= 6 * mut_crit;
				base_ec_cpg -= 6 * 0.3 * dp_crit;
			}
		}else if(cpg.equals("dispatch")){
			cp_per_cpg = 1 + dp_crit;
			base_ec_cpg = 30;
			if(stats.hasSetBonus(SetBonus.T16_2PC))
				base_ec_cpg -= 6 * dp_crit;
		}else{
			System.err.println("Error: " + cpg + " as Combo Point Builder is not valid.");
		}
		
		double ec_cpg = stats.getRealEnergyCost(base_ec_cpg);
		double avg_cpg_per_finisher = finisher_size / cp_per_cpg;
		
		double ec_env = stats.getRealEnergyCost(35);
		double ec_rup = stats.getRealEnergyCost(25);
		
		double avg_relentless_strikes = 5. * finisher_size;
		
		double rup_duration = 4 + 4 * finisher_size;
		double env_duration = 1 + 1 * finisher_size;
		if(stats.hasSetBonus(SetBonus.T15_2PC)){
			rup_duration += 4;
			env_duration += 1;
		}
		
		double energy_cost = avg_cpg_per_finisher * ec_cpg;
		double energy_cost_rup = energy_cost + ec_rup - avg_relentless_strikes;
		double energy_cost_env = energy_cost + ec_env - avg_relentless_strikes;
		
		double ruptures = 1. / rup_duration;
		double energy_regen_left_for_env = active_energy_regen - ruptures * energy_cost_rup;
		
		double cpg_base = ruptures * avg_cpg_per_finisher;
		double aps_so_far = ruptures + cpg_base;
		double gcd = 1.0;
		gcd += stats.getSettings().gcd_latency;
		
		double aps_free = 1. / gcd;
		double aps_left = aps_free - aps_so_far;
		double env_per_sec = energy_regen_left_for_env / energy_cost_env;
		double envs_build_up_aps = env_per_sec * (1 + avg_cpg_per_finisher);
		double cpg_per_sec = env_per_sec * avg_cpg_per_finisher;
		
		if(aps_left < envs_build_up_aps){
			double mod = envs_build_up_aps / aps_left;
			cpg_per_sec = cpg_per_sec / mod;
			env_per_sec = env_per_sec / mod;
		}
		
		aps.add("envenom", env_per_sec);
		cpg_per_sec += cpg_base;
		aps.add("rupture_applications", ruptures);
		double mh_aps = aps.get("envenom");
		double oh_aps = 0;
		if(cpg.equals("mutilate")){
			aps.add(cpg+"_mh", cpg_per_sec);
			aps.add(cpg+"_oh", cpg_per_sec);
			aps.add("dispatch", cpg_per_sec * 0.3);
			mh_aps += aps.get(cpg+"_mh") + aps.get(cpg+"_oh") + aps.get("dispatch");
		}else{
			aps.add(cpg, cpg_per_sec);
			oh_aps += aps.get(cpg);
		}

		mh_aps += aps.get("mh");
		oh_aps += aps.get("oh");

		double env_buff_uptime = Math.min(1.0, env_duration * aps.get("envenom"));
		
		double ip_aps = mh_aps * (0.5 + 0.15 * env_buff_uptime);
		ip_aps += oh_aps * (0.5 + 0.15 * env_buff_uptime);
		
		aps.add("instant_poison", ip_aps);
		
		return aps;
	}

	@Override
	protected double getSpecDamageMod(AttackData ab) {
		double dmgMod = 1.;
		dmgMod *= 1.20; // passive assassins resolve
		if(!ab.getAttack().contains("elemental_force")) {
			dmgMod *= this.vendetta_mult;
		}
		if(Arrays.asList("instant_poison","deadly_poison","envenom","venomous_wound").contains( ab.getAttack() )){
			dmgMod *= (1 + stats.get_assassination_mastery_multiplier());
		}
		return dmgMod;
	}

}
